package com.xinyuan.plug.generator;


import com.xinyuan.plug.model.EntityInfo;
import com.xinyuan.plug.model.FieldInfo;
import org.apache.maven.plugin.logging.Log;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;

import java.io.File;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

public class DaoGenerator {
    private List<String> entityPackages;
    private List<String> excludeClasses;
    private File outputDir;
    private String daoPackage;
    private String tablePrefix;
    private boolean overwrite;
    private Log log;
    private Map<String, String> commonFields;

    public void generate() throws Exception {
        log.info("=== Starting DAO Generation Process ===");
        log.info("Configured entity packages: " + entityPackages);
        log.info("Excluded classes: " + excludeClasses);
        log.info("Output directory: " + outputDir);
        log.info("DAO package: " + daoPackage);
        log.info("Table prefix: " + tablePrefix);

        // Scan entity classes
        List<EntityInfo> entityInfos = scanEntities();

        if (entityInfos.isEmpty()) {
            log.error("!!! No entity classes found !!!");
            log.error("Please check the following configurations:");
            log.error("1. entityPackages configuration: " + entityPackages);
            log.error("2. Whether the project has been compiled (mvn compile)");
            log.error("3. Whether the classpath configuration is correct");
            log.error("4. Whether classpathElements are properly set");
            throw new Exception("No entity classes found, please check configuration");
        }

        log.info("Successfully found " + entityInfos.size() + " entity classes");
        for (EntityInfo entity : entityInfos) {
            log.info(" - " + entity.getClassName());
        }
        // Generate DAO interfaces
        generateDaoInterfaces(entityInfos);

        // Generate Wrapper XML files
        generateWrapperFiles(entityInfos);

        log.info("=== DAO Generation Completed ===");
    }

    private List<EntityInfo> scanEntities() throws Exception {
        List<EntityInfo> entities = new ArrayList<>();

        for (String packageName : entityPackages) {
            log.info("Scanning package: " + packageName);

            List<EntityInfo> packageEntities = scanPackage(packageName);
            entities.addAll(packageEntities);

            log.info("Found " + packageEntities.size() + " entities in package: " + packageName);
        }

        return entities;
    }

    private List<EntityInfo> scanPackage(String packageName) {
        List<EntityInfo> entities = new ArrayList<>();

        try {
            // 回到之前的扫描方式，不使用 FilterBuilder
            Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(packageName)).setScanners(new SubTypesScanner(false)));

            // 获取所有类
            Set<Class<?>> allClasses = reflections.getSubTypesOf(Object.class);
            log.info("Found " + allClasses.size() + " total classes when scanning package: " + packageName);

            for (Class<?> clazz : allClasses) {
                // 关键：精确检查包名，只处理指定包中的类
                if (!clazz.getPackage().getName().equals(packageName)) {
                    continue;
                }

                // Skip excluded classes
                if (isExcluded(clazz)) {
                    log.info("Skipping excluded class: " + clazz.getName());
                    continue;
                }

                // Skip interfaces and abstract classes
                if (clazz.isInterface() || java.lang.reflect.Modifier.isAbstract(clazz.getModifiers())) {
                    log.info("Skipping interface or abstract class: " + clazz.getName());
                    continue;
                }

                // Skip inner classes
                if (clazz.getSimpleName().contains("$")) {
                    log.info("Skipping inner class: " + clazz.getName());
                    continue;
                }

                EntityInfo entityInfo = parseEntityClass(clazz);
                entities.add(entityInfo);
                log.info("Processing entity class: " + clazz.getName());
            }

            log.info("After filtering, found " + entities.size() + " entities in exact package: " + packageName);

        } catch (Exception e) {
            log.error("Failed to scan package: " + packageName, e);
        }

        return entities;
    }

    private boolean isExcluded(Class<?> clazz) {
        if (excludeClasses == null) {
            return false;
        }

        String className = clazz.getName();
        String simpleName = clazz.getSimpleName();

        for (String exclude : excludeClasses) {
            // Support full qualified name exclusion
            if (className.equals(exclude)) {
                return true;
            }
            // Support simple class name exclusion
            if (simpleName.equals(exclude)) {
                return true;
            }
            // Support wildcard exclusion (e.g., com.example.*)
            if (exclude.endsWith(".*") && className.startsWith(exclude.substring(0, exclude.length() - 2))) {
                return true;
            }
        }

        return false;
    }

    private EntityInfo parseEntityClass(Class<?> clazz) {
        EntityInfo entityInfo = new EntityInfo();
        entityInfo.setClassName(clazz.getName());
        entityInfo.setSimpleName(clazz.getSimpleName());
        entityInfo.setPackageName(clazz.getPackage().getName());

        // Generate table name: prefix + class name to underscore
        String tableName = camelToUnderscore(clazz.getSimpleName());
        if (tablePrefix != null && !tablePrefix.isEmpty()) {
            tableName = tablePrefix + "_" + tableName;
        }
        entityInfo.setTableName(tableName);

        // Parse fields
        List<FieldInfo> fields = parseFields(clazz);
        entityInfo.setFields(fields);

        return entityInfo;
    }

    private List<FieldInfo> parseFields(Class<?> clazz) {
        List<FieldInfo> fields = new ArrayList<>();

        // Get all fields from current class and its parent classes (until Object class)
        Class<?> currentClass = clazz;
        while (currentClass != null && currentClass != Object.class) {
            for (Field field : currentClass.getDeclaredFields()) {
                // Skip static fields
                if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                    continue;
                }

                // Skip transient fields
                if (java.lang.reflect.Modifier.isTransient(field.getModifiers())) {
                    continue;
                }

                FieldInfo fieldInfo = parseField(field);
                fields.add(fieldInfo);
            }
            currentClass = currentClass.getSuperclass();
        }

        return fields;
    }

    private FieldInfo parseField(Field field) {
        FieldInfo fieldInfo = new FieldInfo();
        fieldInfo.setFieldName(field.getName());

        // Column name: field name to underscore
        fieldInfo.setColumnName(camelToUnderscore(field.getName()));

        // Determine if it's a primary key (default field named 'id' is primary key)
        fieldInfo.setPrimaryKey("id".equals(field.getName()));

        // Type mapping
        fieldInfo.setJavaType(field.getType().getSimpleName());

        return fieldInfo;
    }

    private String camelToUnderscore(String camelCase) {
        // Convert camel case to underscore
        String result = camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();

        // Handle consecutive uppercase, e.g.: UserID -> user_id
        result = result.replaceAll("([A-Z])([A-Z][a-z])", "$1_$2").toLowerCase();

        return result;
    }

    private void generateDaoInterfaces(List<EntityInfo> entities) throws Exception {
        File daoDir = new File(outputDir, "java/" + daoPackage.replace('.', '/'));
        daoDir.mkdirs();

        for (EntityInfo entity : entities) {
            String daoName = entity.getSimpleName() + "Dao";
            File daoFile = new File(daoDir, daoName + ".java");

            if (!overwrite && daoFile.exists()) {
                log.info("DAO interface already exists, skipping: " + daoName);
                continue;
            }

            String daoContent = generateDaoContent(entity);
            writeFile(daoFile, daoContent);
            log.info("Generated DAO interface: " + daoName);
        }
    }

    private String generateDaoContent(EntityInfo entity) {
        StringBuilder sb = new StringBuilder();

        sb.append("package ").append(daoPackage).append(";\n\n");
        sb.append("import ").append(entity.getClassName()).append(";\n");
        sb.append("import org.apache.ibatis.annotations.Mapper;\n");
        sb.append("import org.apache.ibatis.annotations.Param;\n");
        sb.append("import java.util.List;\n\n");

        sb.append("/**\n");
        sb.append(" * ").append(entity.getSimpleName()).append(" DAO Interface\n");
        sb.append(" * Auto-generated on ").append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date())).append("\n");
        sb.append(" * @version 1.0").append("\n");
        sb.append(" */\n");
        sb.append("public interface ").append(entity.getSimpleName()).append("Dao {\n\n");

        // Insert method
        sb.append("    /**\n");
        sb.append("     * Insert\n");
        sb.append("     */\n");
        sb.append("    int insert(").append(entity.getSimpleName()).append(" entity);\n\n");

        // Update by primary key
        sb.append("    /**\n");
        sb.append("     * Update entity by id\n");
        sb.append("     */\n");
        sb.append("    int updateById(").append(entity.getSimpleName()).append(" entity);\n\n");

        // Update by primary key
        sb.append("    /**\n");
        sb.append("     * Enable entity by id\n");
        sb.append("     */\n");
        sb.append("    int enableById(@Param(\"id\") Long id, @Param(\"released\") Integer released);\n\n");

        // Delete by primary key
        sb.append("    /**\n");
        sb.append("     * Delete by id\n");
        sb.append("     */\n");
        sb.append("    int deleteById(@Param(\"id\") Long id);\n\n");

        // Select by primary key
        sb.append("    /**\n");
        sb.append("     * Select by id\n");
        sb.append("     */\n");
        sb.append("    ").append(entity.getSimpleName()).append(" selectById(@Param(\"id\") Long id);\n\n");

        // Select all
        sb.append("    /**\n");
        sb.append("     * Select all\n");
        sb.append("     */\n");
        sb.append("    List<").append(entity.getSimpleName()).append("> selectAll();\n\n");

        // Select Page
        sb.append("    /**\n");
        sb.append("     * Select Page\n");
        sb.append("     */\n");
        sb.append("    List<").append(entity.getSimpleName()).append("> selectPage(@Param(\"search\") String search, @Param(\"start\") int start, @Param(\"length\") int length);\n\n");

        // Select Page Count
        sb.append("    /**\n");
        sb.append("     * Select Page Count\n");
        sb.append("     */\n");
        sb.append("    int selectPageCount(@Param(\"search\") String search);\n\n");


        sb.append("}");

        return sb.toString();
    }

    private void generateWrapperFiles(List<EntityInfo> entities) throws Exception {
        File xmlDir = new File(outputDir, "resources/mapper");
        xmlDir.mkdirs();

        WrapperGenerator wrapperGenerator = new WrapperGenerator();

        for (EntityInfo entity : entities) {
            String mapperName = entity.getSimpleName() + "Mapper.xml";
            File mapperFile = new File(xmlDir, mapperName);

            if (!overwrite && mapperFile.exists()) {
                log.info("Mapper file already exists, skipping: " + mapperName);
                continue;
            }

            String mapperContent = wrapperGenerator.generateWrapper(entity, daoPackage, commonFields);
            writeFile(mapperFile, mapperContent);
            log.info("Generated Mapper file: " + mapperName);
        }
    }

    private void writeFile(File file, String content) throws Exception {
        file.getParentFile().mkdirs();
        java.nio.file.Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
    }

    // getter/setter methods
    public void setEntityPackages(List<String> entityPackages) {
        this.entityPackages = entityPackages;
    }

    public void setExcludeClasses(List<String> excludeClasses) {
        this.excludeClasses = excludeClasses;
    }

    public void setOutputDir(File outputDir) {
        this.outputDir = outputDir;
    }

    public void setDaoPackage(String daoPackage) {
        this.daoPackage = daoPackage;
    }

    public void setTablePrefix(String tablePrefix) {
        this.tablePrefix = tablePrefix;
    }

    public void setOverwrite(boolean overwrite) {
        this.overwrite = overwrite;
    }

    public void setLog(Log log) {
        this.log = log;
    }

    public DaoGenerator setCommonFields(Map<String, String> commonFields) {
        this.commonFields = commonFields;
        return this;
    }
}